{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 简单工厂模式 v.s. 工厂方法模式\n",
    "\n",
    "以简单计算器为例，对比一下简单工厂模式和工厂方法模式的区别。\n",
    "\n",
    "### 简单工厂模式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input a number:99\n",
      "input a operater(+ - * /):/\n",
      "input a number:9\n",
      "11.0\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "\n",
    "class Operation():\n",
    "    \"\"\"\n",
    "    抽象产品类(运算符类)\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "\n",
    "    def __init__(self):\n",
    "        self.result = None\n",
    "\n",
    "    @abstractmethod\n",
    "    def get_result(self):\n",
    "        pass\n",
    "\n",
    "    \n",
    "class AddOperation(Operation):\n",
    "    \"\"\"\n",
    "    具体产品类(加法运算符)\n",
    "    \"\"\"\n",
    "    def get_result(self, number_a, number_b):\n",
    "        self.result = number_a + number_b\n",
    "        return self.result\n",
    "\n",
    "    \n",
    "class SubOperation(Operation):\n",
    "    \"\"\"\n",
    "    具体产品类(减法运算符)\n",
    "    \"\"\"\n",
    "    def get_result(self, number_a, number_b):\n",
    "        self.result = number_a - number_b\n",
    "        return self.result\n",
    "\n",
    "    \n",
    "class MulOperation(Operation):\n",
    "    \"\"\"\n",
    "    具体产品类(乘法运算符)\n",
    "    \"\"\"\n",
    "    def get_result(self, number_a, number_b):\n",
    "        self.result = number_a * number_b\n",
    "        return self.result\n",
    "    \n",
    "    \n",
    "class DivOperation(Operation):\n",
    "    \"\"\"\n",
    "    具体产品类(除法运算符)\n",
    "    \"\"\"\n",
    "    def get_result(self, number_a, number_b):\n",
    "        if number_b == 0:\n",
    "            print(\"With operator '/', the second number can not be zero.\")\n",
    "            return self.result\n",
    "        self.result = number_a / number_b\n",
    "        return self.result\n",
    "    \n",
    "    \n",
    "class OperationFactory():\n",
    "    \"\"\"\n",
    "    产品工厂类\n",
    "    \"\"\"\n",
    "    @classmethod\n",
    "    def create_operate(self, operator):\n",
    "        oper = None\n",
    "        if operator == \"+\":\n",
    "            oper = AddOperation()\n",
    "        elif operator == \"-\":\n",
    "            oper = SubOperation()\n",
    "        elif operator == \"*\":\n",
    "            oper = MulOperation()\n",
    "        elif operator == \"/\":\n",
    "            oper = DivOperation()\n",
    "        else:\n",
    "            print(\"Wrong operator.\")\n",
    "        return oper\n",
    "\n",
    "number_a = int(input(\"input a number:\"))\n",
    "operator = str(input(\"input a operater(+ - * /):\"))\n",
    "number_b = int(input(\"input a number:\"))\n",
    "\n",
    "oper = OperationFactory.create_operate(operator)\n",
    "print(oper.get_result(number_a, number_b))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 工厂方法模式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input a number:99\n",
      "input a operater(+ - * /):/\n",
      "input a number:11\n",
      "9.0\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "\n",
    "class IFactory():\n",
    "    \"\"\"\n",
    "    通用工厂接口\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def create_operation(self):\n",
    "        pass\n",
    "\n",
    "    \n",
    "class AddFactory(IFactory):\n",
    "    \"\"\"\n",
    "    实现工厂接口的加法工厂类\n",
    "    \"\"\"\n",
    "    def create_operation(self):\n",
    "        return AddOperation()\n",
    "\n",
    "    \n",
    "class SubFactory(IFactory):\n",
    "    \"\"\"\n",
    "    实现工厂接口的剑法工厂类\n",
    "    \"\"\"\n",
    "    def create_operation(self):\n",
    "        return SubOperation()\n",
    "\n",
    "    \n",
    "class MulFactory(IFactory):\n",
    "    \"\"\"\n",
    "    实现工厂接口的乘法工厂类\n",
    "    \"\"\"\n",
    "    def create_operation(self):\n",
    "        return MulOperation()\n",
    "    \n",
    "\n",
    "class DivFactory(IFactory):\n",
    "    \"\"\"\n",
    "    实现工厂接口的除法工厂类\n",
    "    \"\"\"\n",
    "    def create_operation(self):\n",
    "        return DivOperation()\n",
    "    \n",
    "\n",
    "def main():\n",
    "    number_a = int(input(\"input a number:\"))\n",
    "    operator = str(input(\"input a operater(+ - * /):\"))\n",
    "    number_b = int(input(\"input a number:\"))\n",
    "\n",
    "    if operator == \"+\":\n",
    "        oper_factory = AddFactory()\n",
    "    elif operator == \"-\":\n",
    "        oper_factory = SubFactory()\n",
    "    elif operator == \"*\":\n",
    "        oper_factory = MulFactory()\n",
    "    elif operator == \"/\":\n",
    "        oper_factory = DivFactory()\n",
    "    else:\n",
    "        print(\"Wrong operator.\")\n",
    "\n",
    "    oper = oper_factory.create_operation()\n",
    "    print(oper.get_result(number_a, number_b))\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "\n",
    "#### 工厂方法更复杂了？\n",
    "\n",
    "如果需要增加其他运算，比如求M的N次方。\n",
    "\n",
    "在简单工厂模式里，先增加一个求M的N次方的产品类，然后更改工厂类的if判断增加分支即可。\n",
    "\n",
    "在工厂方法模式里，先增加一个求M的N次方的产品类，还要新增一个相关工厂类，最后还有修改客户端代码。\n",
    "\n",
    "这就是简单工厂和工厂方法的区别所在。简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断，根据客户端的选择条件动态实例化相关类，对于客户端来说，去除了与具体产品的依赖。但是，如果要增加一个新的功能，比如求M的N次方，需要更改工厂类的if判断分支条件，修改原有的类？违背了开放-封闭原则，这可不是好方法。所以就需要工厂方法模式来处理。\n",
    "\n",
    "## 工厂方法模式\n",
    "\n",
    "工厂方法模式，定义一个用于创建对象的接口，让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类[DP]。\n",
    "\n",
    "相当于将简单工厂中的工厂类，变成了一个工厂抽象接口和多个具体生成对象的工厂，于是我们要增加求M的N次方的功能，就不需要更改工厂类，只需要增加此功能的运算类和相应的工厂类即可。这样整个工厂和产品体系其实都没有修改，而只是扩展，这就完全符合了开放-封闭原则。\n",
    "\n",
    "但是，工厂方法模式是现实，客户端需要决定实例化哪一个工厂来实现运算类，选择判断的问题还是存在的，也就是说，工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。要增加新功能，本来修改工厂类，现在修改客户端了。\n",
    "\n",
    "## 题目\n",
    "\n",
    "木叶学校组织学雷锋活动，让鸣人，小樱，佐助帮敬老院的老人扫地，洗衣，买米，如何实现？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "扫地\n",
      "洗衣\n",
      "买米\n"
     ]
    }
   ],
   "source": [
    "class LeiFeng():\n",
    "    \n",
    "    def sweep(self):\n",
    "        print(\"扫地\")\n",
    "    \n",
    "    def wash(self):\n",
    "        print(\"洗衣\")\n",
    "    \n",
    "    def buy_rice(self):\n",
    "        print(\"买米\")\n",
    "        \n",
    "class Student(LeiFeng):\n",
    "    pass\n",
    "\n",
    "def main():\n",
    "    mingren = Student()\n",
    "    xiaoying = Student()\n",
    "    zuozhu = Student()\n",
    "    \n",
    "    mingren.sweep()\n",
    "    xiaoying.wash()\n",
    "    zuozhu.buy_rice()\n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "- 学生都会毕业，但是帮助老人是长期工作，所以每次不同的人帮助老人，都需要改客户端代码，而且老人不可能知道所有来帮忙的学生的名字；\n",
    "- 除了学生，社区志愿者也可以帮助老人\n",
    "\n",
    "如何用简单工厂方法解决上述问题？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "买米\n",
      "洗衣\n",
      "扫地\n"
     ]
    }
   ],
   "source": [
    "class Volunteer(LeiFeng):\n",
    "    pass\n",
    "\n",
    "\n",
    "class SimpleFactory():\n",
    "    \n",
    "    @classmethod\n",
    "    def create_leifeng(self, leifeng_type):\n",
    "        self.leifeng = None\n",
    "        if leifeng_type == \"学生\":\n",
    "            self.leifeng = Student()\n",
    "        elif leifeng_type == \"志愿者\":\n",
    "            self.leifeng = Volunteer()\n",
    "        else:\n",
    "            print(\"ERROR LeiFeng Type\")\n",
    "        return self.leifeng\n",
    "    \n",
    "def main():\n",
    "    studentA = SimpleFactory.create_leifeng(\"学生\")\n",
    "    studentA.buy_rice()\n",
    "    studentB = SimpleFactory.create_leifeng(\"学生\")\n",
    "    studentB.wash()\n",
    "    studentC = SimpleFactory.create_leifeng(\"学生\")\n",
    "    studentB.sweep()\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "- 好的地方，客户端的代码，如果要换志愿者，只需要换参数即可；\n",
    "- 坏的地方，在任何实例化的时候都需要写一句`SimpleFactory.create_leifeng(\"学生\")`，这会导致大量重复，在修改为`志愿者`的时候非常麻烦，可以用工厂方法解决这个问题；"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "扫地\n",
      "洗衣\n",
      "买米\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "\n",
    "class ILeiFengFactory():\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def create_leifeng(self):\n",
    "        pass\n",
    "\n",
    "    \n",
    "class StudentFactory(ILeiFengFactory):\n",
    "    \n",
    "    def create_leifeng(self):\n",
    "        return Student()\n",
    "\n",
    "    \n",
    "class VolunteerFactory(ILeiFengFactory):\n",
    "    \n",
    "    def create_leifeng(self):\n",
    "        return Volunteer()\n",
    "\n",
    "def main():\n",
    "    leifeng_factory = StudentFactory()\n",
    "    stu1 = leifeng_factory.create_leifeng()\n",
    "    stu2 = leifeng_factory.create_leifeng()\n",
    "    stu3 = leifeng_factory.create_leifeng()\n",
    "    \n",
    "    stu1.sweep()\n",
    "    stu2.wash()\n",
    "    stu3.buy_rice()\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "- 此时如果要将学生改成志愿者，只需要修改一行代码即可；\n",
    "- 工厂方法克服了简单工厂违背开放-封闭原则的缺点，又保持了封装对象创建过程的优点;\n",
    "\n",
    "## 总结\n",
    "简单工厂和工厂方法都是集中封装了对象的创建，使得要更换对象时，不需要做大的改动就可以实现，降低了客户程序和产品对象的耦合。\n",
    "\n",
    "工厂方法是简单工厂模式的进一步抽象和推广，由于使用了多态性，工厂方法模式保持了简单工厂模式的优点，而且克服了它的缺点。但缺点是由于每加一个产品，就需要加一个产品工厂类，增加了额外开发量。\n",
    "\n",
    "另外，工厂方法还是没有避免修改客户端的代码，可以利用`反射`解决避免分支判断的问题。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
